package indigo.ueditor.handler;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Files;

import indigo.ueditor.PathPatternResolver;
import indigo.ueditor.UEditorException;
import indigo.ueditor.UEditorFileManager;
import indigo.ueditor.UEditorHandler;
import indigo.ueditor.UEditorState;

public class RemoteFetchHandler implements UEditorHandler {
	private final HttpClient httpClient = HttpClients.createDefault();

	private final String name;
	private final UEditorFileManager fileManager;
	private final String parameterName;
	private final String savePathPattern;
	private final String fileBaseName;
	private final long maxSize;
	private final Set<String> allowedSuffixes;
	private final Set<String> forbiddenHosts;
	private final Map<String, String> contentTypeSuffixMapping;

	public RemoteFetchHandler(String name, UEditorFileManager fileManager,
			String parameterName, String savePathPattern, String fileBaseName, long maxSize,
			Set<String> allowedSuffixes, Set<String> forbiddenHosts,
			Map<String, String> contentTypeSuffixMapping) {
		this.name = name;
		this.fileManager = fileManager;
		this.parameterName = parameterName;
		this.savePathPattern = savePathPattern;
		this.fileBaseName = fileBaseName;
		this.maxSize = maxSize;
		this.allowedSuffixes = allowedSuffixes;
		this.forbiddenHosts = forbiddenHosts;
		this.contentTypeSuffixMapping = contentTypeSuffixMapping;
	}

	@Override
	public String getName() {
		return name;
	}

	protected boolean isHostAllowed(String host) {
		if (forbiddenHosts.contains(host)) {
			return false;
		}
		try {
			return InetAddress.getByName(host).isSiteLocalAddress();
		} catch (UnknownHostException e) {
			return false;
		}
	}

	protected String getFileName(String contentType) {
		if (contentType == null) {
			throw new UEditorException(UEditorState.NOT_ALLOW_FILE_TYPE);
		}
		String suffix = contentTypeSuffixMapping.get(contentType);
		if (suffix == null || !allowedSuffixes.contains(suffix)) {
			throw new UEditorException(UEditorState.NOT_ALLOW_FILE_TYPE);
		}
		return fileBaseName + contentTypeSuffixMapping.get(contentType);
	}

	@Override
	public Object handle(HttpServletRequest request) {
		String[] urls = request.getParameterValues(parameterName);
		if (urls == null || urls.length == 0) {
			throw new UEditorException(UEditorState.NOTFOUND_UPLOAD_DATA);
		}

		List<Map<String, Object>> results = Lists.newArrayList();
		for (String url : urls) {
			try {
				String host = new URL(url).getHost();
				if (isHostAllowed(host)) {
					throw new UEditorException(UEditorState.PREVENT_HOST);
				}

				HttpGet req = new HttpGet(url.toString());
				HttpResponse res = httpClient.execute(req);
				try {
					if (res.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
						throw new UEditorException(UEditorState.CONNECTION_ERROR);
					}
					HttpEntity entity = res.getEntity();
					try {
						Header contentType = entity.getContentType();
						String contentTypeValue = contentType != null ? contentType.getValue() : null;
						String fileName = getFileName(contentTypeValue);
						String fileExtension = Files.getFileExtension(fileName);
						String fileSuffix = fileExtension.isEmpty() ? "" : "." + fileExtension;

						PathPatternResolver resolver = new PathPatternResolver();
						resolver.putVariable("filename", fileName);
						String savePath = resolver.resolve(savePathPattern) + fileSuffix;

						fileManager.createFile(savePath, entity.getContent(), maxSize);

						Map<String, Object> result = Maps.newHashMap();
						result.put("state", UEditorState.SUCCESS.getMessage());
						result.put("url", savePath);
						result.put("source", url);
						results.add(Collections.unmodifiableMap(result));
					} finally {
						EntityUtils.consume(entity);
					}
				} finally {
					if (res instanceof CloseableHttpResponse) {
						((CloseableHttpResponse) res).close();
					}
				}
			} catch (UEditorException e) {
				results.add(Collections.singletonMap("state", e.getState().getMessage()));
			} catch (IOException e) {
				results.add(Collections.singletonMap("state", UEditorState.IO_ERROR.getMessage()));
			}
		}

		Map<String, Object> result = Maps.newHashMap();
		result.put("state", UEditorState.SUCCESS.getMessage());
		result.put("list", results);
		return Collections.unmodifiableMap(result);
	}
}
